package com.junzhiit.nengji.cms.web.controller;

import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import com.bainaeco.framework.base.exception.BaseException;
import com.bainaeco.framework.enums.Lang;
import com.bainaeco.framework.util.JsonUtil;
import com.junzhiit.nengji.cms.service.MessageService;
import com.junzhiit.nengji.cms.web.util.CMSCodeStatus;
import com.junzhiit.nengji.cms.web.util.Constants;
import com.junzhiit.nengji.cms.web.util.URLAnalysis;
import com.netflix.hystrix.exception.HystrixBadRequestException;

@Controller
public class AppErrorController extends BaseController implements ErrorController {

    private static AppErrorController appErrorController;

    @Autowired
    private MessageService messageService;

    /**
     * Error Attributes in the Application
     */
    @Autowired
    private ErrorAttributes errorAttributes;

    /**
     * Controller for the Error Controller
     *
     * @param errorAttributes
     * @return
     */

    public AppErrorController(final ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }

    public AppErrorController() {
        if (appErrorController == null) {
            appErrorController = new AppErrorController(errorAttributes);
        }
    }

    @RequestMapping(value = Constants.PAGE_NOT_FOUND_PATH, produces = Constants.CONTENT_TYPE_HTML)
    public ModelAndView pageNotFoundHtml(final HttpServletRequest request, final HttpServletResponse response) {
        final Map<String, Object> model = getErrorAttributes(request, getTraceParameter(request));
        // return error JSON page
        final String json = JsonUtil.toJson(model);
        model.put(Constants.MESSAGE_JSON, json);

        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        // return velocity templates json.vm
        response.setContentType(Constants.CONTENT_TYPE_JSON);
        return new ModelAndView("json", model);
    }

    /**
     * Supports the HTML Error View
     *
     * @param request
     * @return
     */
    @RequestMapping(value = Constants.ERROR_PATH, produces = Constants.CONTENT_TYPE_HTML)
    public ModelAndView errorHtml(final HttpServletRequest request, final HttpServletResponse response) {
        final Map<String, Object> model = getErrorAttributes(request, getTraceParameter(request));
        // return error JSON page
        final String json = JsonUtil.toJson(model);
        logger.error(json);
        model.put(Constants.MESSAGE_JSON, json);

        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        // return velocity templates json.vm
        response.setContentType(Constants.CONTENT_TYPE_JSON);
        return new ModelAndView("json", model);
    }

    /**
     * Supports other formats like JSON, XML
     *
     * @param request
     * @return
     */
    @RequestMapping(value = {Constants.ERROR_PATH, Constants.PAGE_NOT_FOUND_PATH})
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
        final Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request));
        final HttpStatus status = getStatus(request);
        final ResponseEntity<Map<String, Object>> response =  new ResponseEntity<Map<String, Object>>(body, status);
        logger.error(response.toString());
        return response;
    }

    /**
     * Returns the path of the error page.
     *
     * @return the error path
     */
    @Override
    public String getErrorPath() {
        return Constants.ERROR_PATH;
    }

    private boolean getTraceParameter(final HttpServletRequest request) {
        final String parameter = request.getParameter("trace");
        if (parameter == null) {
            return false;
        }
        return !"false".equals(parameter.toLowerCase(Locale.ENGLISH));
    }

    private String getLangFromRequest(final HttpServletRequest request) {
        String lang = request.getParameter(Constants.LANG);

        if (StringUtils.isBlank(lang)) {
            final URLAnalysis urlAnalysis = (URLAnalysis) request.getAttribute(Constants.URL_ANALYSIS);
            if (urlAnalysis != null) {
                if (urlAnalysis.getParam(Constants.LANG) != null) {
                    lang = urlAnalysis.getParam(Constants.LANG);
                }
            }
        }

        lang = StringUtils.isNotBlank(lang) ? lang : Lang.getDefaultLang().toStringValue();

        return lang;
    }

    private Map<String, Object> getErrorAttributes(final HttpServletRequest request, final boolean includeStackTrace) {
        final RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        final Map<String, Object> map = this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
        final Exception ex = (Exception) errorAttributes.getError(requestAttributes);

        if (ex != null) {
            logger.error(ex.getMessage(), ex);

            // is a custom exception ?
            if (ex instanceof BaseException) {
                final BaseException baseEx = (BaseException) ex;
                final int status = baseEx.getErrorCode();
                final int httpStatus = baseEx.getHttpStatus();
                String message = null;
                final String lang = getLangFromRequest(request);
//                logger.info("lang:" + lang);
                logger.info("status:" + status);
                if (StringUtils.isNotEmpty(baseEx.getMessage()) && baseEx.getMessage().equals("secretKey is required")) {
                    message = messageService.getMessage("touch.id.reset", baseEx.getArgs(), lang);
                } else {
                    message = messageService.getMessage(status, baseEx.getArgs(), lang);
                }

                if (baseEx.getData() != null) {
                    map.put("data", baseEx.getData());
                }

                // custom errorCode
                map.put("status", status);
                // custom errorMessage
                if (StringUtils.isNotEmpty(message)) {
                    map.put("message", message);
                }
                request.setAttribute("javax.servlet.error.status_code", httpStatus);
//                logger.info("message:" + message);
            } 
            else if(ex instanceof HystrixBadRequestException) {
            	String message = map.get("message").toString();
            	Map<String, Object> exMap = JsonUtil.toMap(message);
            	map.put("timestamp", exMap.get("timestamp").toString());
            	map.put("status", exMap.get("status").toString());
            	map.put("error", exMap.get("error").toString());
            	map.put("exception", exMap.get("exception").toString());
            	map.put("path", exMap.get("path").toString());
            	map.put("message", exMap.get("message").toString());
            	request.setAttribute("javax.servlet.error.status_code", CMSCodeStatus.SUCCESS);
            }
            else {
                logger.error(ex.getMessage(), ex);
                logger.info("This not base error, is unknow error. developer may should check code.");
                String message = null;
                final String lang = getLangFromRequest(request);
//                logger.info("lang:" + lang);
                message = messageService.getMessage(CMSCodeStatus.FAILED, null, lang);

                // custom errorCode
                map.put("status", CMSCodeStatus.FAILED);
                // custom errorMessage
                if (StringUtils.isNotEmpty(message)) {
                    map.put("message", message);
                }
//                logger.info("message:" + message);
            }
        }

        logger.debug("AppErrorController.method [error info]: status-" + map.get("status") + ", request url-"
                + map.get("path"));
        return map;
    }

    private HttpStatus getStatus(final HttpServletRequest request) {
        final Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode != null) {
            try {
                return HttpStatus.valueOf(statusCode);
            } catch (final Exception ex) {
                logger.error(ex.getMessage(), ex);
            }
        }
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }

}
